import java.io.*;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
import org.apache.commons.io.IOUtils;

/**
 * Esta clase encripta y desencripta un archivo usando CipherStreams
 * usando una clave RSA de 4096 bits
 */
public class FileEncryptorRSA {
  
    //Sufijo para los archivos encriptados
    private static final String ENCRYPTED_FILENAME_SUFFIX=".encrypted";

    //Sufijo apra los archivos desencriptados
    private static final String DECRYPTED_FILENAME_SUFFIX=".decrypted";

    public static void main (String[] args) {
        try {
            usage();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void usage() throws Exception {
        createKey();
        encrypt("C://prueba.txt");
        decrypt("C://prueba.txt.encrypted");
        //System.exit(1);
    }

  /**
   * Genera un par de claves pública y privada RSA de 4096 bits y las almacena
   *  en ficheros de texto
   */
  public static void createKey() throws Exception {
    System.out.println("Generating an RSA keypair...");
    System.out.println(new Date(System.currentTimeMillis()));
    
    // Crear la clave RSA
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    keyPairGenerator.initialize(2048);
    KeyPair keyPair = keyPairGenerator.genKeyPair();

    System.out.println("Done generating the keypair.\n");
    System.out.println(new Date(System.currentTimeMillis()));

    String publicKeyFilename = "C://publicKey.txt";

    // Decodifica la clave. Por defecto están en X.509 
    byte[] publicKeyBytes = keyPair.getPublic().getEncoded();

    // Escribimos la clave publica codificada
    FileOutputStream fos = new FileOutputStream(publicKeyFilename);
    fos.write(publicKeyBytes);
    fos.flush();
    fos.close();

    // Se hace lo mismo con la clave privada
    String privateKeyFilename = "C://privateKey.txt";

    // Obtenemos el tipo de codificacion
    byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();

    fos = new FileOutputStream(privateKeyFilename);
    fos.write(privateKeyBytes);
    fos.flush();
    fos.close();
  }

  /**
   * Encripta el archivo pasado como parámetro con una clave RSA leida del
   *  archivo publickey
   */
  public static void encrypt(String fileInput) throws Exception {
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    String publicKeyFilename = "C://publicKey.txt";
    
    System.out.println("Load the public key bytes..");
    System.out.println("Inicio encrpypt: " + new Date(System.currentTimeMillis()));
    // Cargamos la clave pública
    FileInputStream fis = new FileInputStream(publicKeyFilename);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    int theByte = 0;
    while ((theByte = fis.read()) != -1)
    {
      baos.write(theByte);
    }
    fis.close();

    byte[] keyBytes = baos.toByteArray();
    baos.close();
    
    System.out.println("Loaded");

    // Decodificamos la clave RSA que estará en X.509.
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
    PublicKey publicKey = keyFactory.generatePublic(keySpec);

    // Archivo a cifrar
    FileInputStream input = new FileInputStream(fileInput);
    
    // Abrimos el archivo de salida encriptado
    String fileOutput = fileInput + ENCRYPTED_FILENAME_SUFFIX;
    DataOutputStream output = new DataOutputStream (new FileOutputStream(fileOutput));
    
    // Creamos un Cipher usando la clave
    Cipher rsaCipher = Cipher.getInstance("RSA/ECB/NOPADDING", "BC");
    rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
    //CipherOutputStream cos = new CipherOutputStream(output, rsaCipher);
    
    System.out.println("Encrypting the file...");

    byte [] theBytes = IOUtils.toByteArray(input);
//    theByte = 0;
//    while ((theByte = input.read()) != -1){
//        cos.write(theByte);
//    }
//    cos.flush();
//    cos.close();
    rsaCipher.doFinal(theBytes);
    CipherOutputStream cos = new CipherOutputStream(output, rsaCipher);
    theByte = 0;
    while ((theByte = input.read()) != -1){
        cos.write(theByte);
    }
    cos.flush();
    cos.close();
    
    
    output.close();
    input.close();
    System.out.println("Done: " + new Date(System.currentTimeMillis()));
  }

  /**
   * Desencripta el archivo pasado por parámetro
   */
  private static void decrypt(String fileInput) throws Exception {
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    String privateKeyFilename = "C://privateKey.txt";
    System.out.println("Inicio decrypt: " + new Date(System.currentTimeMillis()));
    int i;

    // Cargamos la clave privada
    FileInputStream fis = new FileInputStream(privateKeyFilename);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    int theByte = 0;
    while ((theByte = fis.read()) != -1)
    {
      baos.write(theByte);
    }
    fis.close();

    byte[] keyBytes = baos.toByteArray();
    baos.close();

    // Las claves privadas están codificadas en PKCS#8.
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

    // Creamos un Cipher con la clave leida
    Cipher rsaCipher = Cipher.getInstance("RSA/ECB/NOPADDING", "BC");

    // Desencriptamos
    rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);
    System.out.println("Decrypting the file...");
    
    fis = new FileInputStream(fileInput);
    CipherInputStream cis = new CipherInputStream(fis, rsaCipher);
    
    // Abrimos el fichero de salida donde se desencriptará
    String fileOutput = fileInput + DECRYPTED_FILENAME_SUFFIX;
    FileOutputStream fos = new FileOutputStream(fileOutput);
    
    theByte = 0;
    while ((theByte = cis.read()) != -1) {
      fos.write(theByte);
    }
    fos.flush();
    fos.close();
    cis.close();
    fis.close();
    System.out.println("Done:" + new Date(System.currentTimeMillis()));
  }
}